package com.synergy.stockflow.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.Optional;

public class JwtUtils {

    // 默认过期时间：24小时（单位：毫秒）
    public static final long DEFAULT_EXPIRATION = 86400000;

    /**
     * 构建 JWT Token 字符串（支持自定义声明）
     *
     * @param subject     主题（如用户ID）
     * @param extraClaims 自定义声明（可为 null）
     * @param secret      签名密钥（推荐至少 64 字节）
     * @param expiration  过期时间（毫秒）
     * @return JWT Token 字符串
     */
    public static String build(String subject, Map<String, Object> extraClaims,
                               String secret, long expiration) {
        // 将字符串密钥转换为 SecretKey 对象（使用 HS512 算法）
        SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));

        Date currentTime = new Date(System.currentTimeMillis());

        // 构建 JWT Token
        return Jwts.builder()
                .claims(extraClaims != null ? extraClaims : Jwts.claims().build()) // 使用传入的 claims 或空对象
                .subject(subject) // 设置 subject (sub)
                .issuedAt(currentTime) // 设置签发时间
                .expiration(new Date(currentTime.getTime() + expiration)) // 设置过期时间
                .signWith(secretKey, Jwts.SIG.HS512) // 使用 HS512 算法签名
                .compact(); // 生成最终的 token 字符串
    }

    /**
     * 解析 JWT Token 获取 Claims（安全处理异常）
     *
     * @param token  待解析的 Token
     * @param secret 密钥
     * @return Optional<Claims>，如果解析失败返回 empty
     */
    public static Optional<Claims> parse(String token, String secret) {
        try {
            // 创建 HMAC-SHA 密钥
            SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
            return Optional.of(
                    Jwts.parser()
                            .verifyWith(secretKey) // 使用指定密钥验证签名
                            .build()
                            .parseSignedClaims(token) // 解析并验证签名
                            .getPayload() // 获取 payload 部分（即 Claims）
            );
        } catch (JwtException e) {
            // 捕获 JWT 相关异常（如签名错误、格式错误等），返回 empty
            return Optional.empty();
        }
    }

    /**
     * 构建 Token（默认过期时间，不带自定义声明）
     *
     * @param subject 主题（如用户ID）
     * @param secret  密钥
     * @return JWT Token 字符串
     */
    public static String generateToken(String subject, String secret) {
        return build(subject, null, secret, DEFAULT_EXPIRATION);
    }

    /**
     * 构建 Token（指定过期时间，不带自定义声明）
     *
     * @param subject    主题（如用户ID）
     * @param secret     密钥
     * @param expiration 过期时间（毫秒）
     * @return JWT Token 字符串
     */
    public static String generateToken(String subject, String secret, long expiration) {
        return build(subject, null, secret, expiration);
    }

    /**
     * 提取任意字段的值（claim）
     *
     * @param token    待解析的 Token
     * @param secret   密钥
     * @param claimKey 要提取的字段名（如 "username"）
     * @return claim 值（Object 类型），如果不存在或解析失败则返回 null
     */
    public static Object extractClaim(String token, String secret, String claimKey) {
        return parse(token, secret)
                .map(claims -> claims.get(claimKey)) // 如果存在则取出 claim
                .orElse(null); // 否则返回 null
    }

    /**
     * 获取 Token 中的 subject（用户标识）
     *
     * @param token  待解析的 Token
     * @param secret 密钥
     * @return subject 字段值，如果不存在或解析失败则返回 null
     */
    public static String extractSubject(String token, String secret) {
        return parse(token, secret)
                .map(Claims::getSubject) // 获取 subject 字段
                .orElse(null);
    }

    /**
     * 判断 Token 是否已过期
     *
     * @param token  待解析的 Token
     * @param secret 密钥
     * @return true if expired or invalid token
     */
    public static boolean isExpired(String token, String secret) {
        return parse(token, secret)
                .map(claims -> claims.getExpiration().before(new Date())) // 检查是否过期
                .orElse(true); // 如果解析失败也认为过期
    }

    public static long getExpiration(String token, String secret) {
        return parse(token, secret).map(claims -> claims.getExpiration().getTime()).orElse(0L);
    }
}
